﻿namespace NuGetCop.MSBuild.Tasks
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.Contracts;
    using System.Linq;
    using Microsoft.Build.Framework;
    using NuGetCop.MSBuild.Tasks.Configuration;
    using NuGetCop.MSBuild.Tasks.Foundation;
    using NuGetCop.MSBuild.Tasks.Resources;

    public sealed class CheckNuGetPackages : CancelableTask
    {
        private readonly INuGetCopSettingsProvider settingsProvider;
        private readonly INuGetPackageRepositoryFactory packageRepositoryFactory;
        private readonly INuGetPackageChecker packageChecker;
        private readonly INuGetPackageCheckResultAggregator resultAggregator;

        public CheckNuGetPackages() : this(
            new NuGetCopSettingsProvider(),
            new NuGetPackageRepositoryFactory(),
            new NuGetPackageChecker(),
            new NuGetPackageCheckResultAggregator())
        {
        }

        internal CheckNuGetPackages(
            INuGetCopSettingsProvider settingsProvider,
            INuGetPackageRepositoryFactory packageRepositoryFactory,
            INuGetPackageChecker packageChecker,
            INuGetPackageCheckResultAggregator resultAggregator)
        {
            Contract.Requires(settingsProvider != null);
            Contract.Requires(packageRepositoryFactory != null);
            Contract.Requires(packageChecker != null);
            Contract.Requires(resultAggregator != null);

            this.settingsProvider = settingsProvider;
            this.packageRepositoryFactory = packageRepositoryFactory;
            this.packageChecker = packageChecker;
            this.resultAggregator = resultAggregator;
        }

        [Required]
        public string RulesConfigurationFilePath { get; set; }

        [Required]
        public string PackagesConfigurationFilePath { get; set; }

        public bool TreatErrorsAsWarnings { get; set; }

        public override bool Execute()
        {
            bool executionResult;

            try
            {
                var settings = LoadSettings();
                executionResult = Execute(settings);
            }
            catch (Exception ex)
            {
                Log.LogErrorFromException(ex);

                throw;
            }
            
            return executionResult;
        }

        private NuGetCopSettings LoadSettings()
        {
            if (string.IsNullOrEmpty(RulesConfigurationFilePath))
            {
                throw new InvalidOperationException(Msg.ConfigurationFilePathIsNotSpecified);
            }

            var rulesUri = new Uri(RulesConfigurationFilePath);
            var resultSettings = settingsProvider.GetSettings(rulesUri);

            return resultSettings;
        }

        private bool Execute(NuGetCopSettings settings)
        {
            Contract.Requires(settings != null);
            Contract.Assume(Log != null);

            var rules = settings.Rules;
            if (!rules.Eval(x => x.Any()))
            {
                Log.LogMessage(Msg.NoSpecifiedRules);

                return true;
            }

            if (string.IsNullOrEmpty(PackagesConfigurationFilePath))
            {
                throw new InvalidOperationException(Msg.PackagesConfigPathIsNotSpecified);
            }

            var packageRepository = packageRepositoryFactory.Create(PackagesConfigurationFilePath);

            var checkResults = rules
                .Select(rule => Execute(rule, packageRepository))
                .Where(result => result != null)
                .ToArray();

            var aggregatedResult = resultAggregator.Aggregate(checkResults, packageRepository);
            LogErrors(aggregatedResult.Errors);

            var taskSucceeded = TreatErrorsAsWarnings || aggregatedResult.IsSuccess();
            return taskSucceeded;
        }

        private INuGetPackageCheckResult Execute(NuGetCopRuleSettings rule, INuGetPackageRepository packageRepository)
        {
            Contract.Requires(rule != null);
            Contract.Requires(packageRepository != null);
            Contract.Assume(Log != null);

            if (CancellationToken.IsCancellationRequested)
            {
                Log.LogMessage(Msg.TaskIsCanceledFormat, GetType().Name);
                return null;
            }
            
            Log.LogMessage(
                Msg.CheckingRuleFormat, 
                rule.PackageId,
                rule.Version,
                rule.TargetFramework);

            var checkResult = packageChecker.CheckNuGetPackageRule(rule, packageRepository);
            return checkResult;
        }
        
        private void LogErrors(IEnumerable<string> errors)
        {
            Contract.Requires(errors != null);
            Contract.Assume(Log != null);

            foreach (var error in errors)
            {
                if (TreatErrorsAsWarnings)
                {
                    Log.LogWarning(error);
                }
                else
                {
                    Log.LogError(error);
                }
            }
        }
        
        [ContractInvariantMethod]
        private void ObjectInvariant()
        {
            Contract.Invariant(settingsProvider != null);
            Contract.Invariant(packageRepositoryFactory != null);
            Contract.Invariant(packageChecker != null);
            Contract.Invariant(resultAggregator != null);
        }
    }
}
